#![no_std]

use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};

mod raw;
pub use raw::*;

mod endian;
pub(crate) use endian::*;

mod fd;
pub use fd::*;

mod file;
pub use file::*;

pub trait ElfHdr {
    fn e_ident(&self) -> &[u8];
    fn e_endian(&self) -> u8;
    fn e_class(&self) -> u8;
    fn e_machine(&self) -> u16;
    fn e_version(&self) -> u32;
    fn e_entry(&self) -> u64;
    fn e_phoff(&self) -> u64;
    fn e_shoff(&self) -> u64;
    fn e_flags(&self) -> u64;
    fn e_ehsize(&self) -> u16;
    fn e_phentsize(&self) -> u16;
    fn e_phnum(&self) -> u16;
    fn e_shentsize(&self) -> u16;
    fn e_shnum(&self) -> u16;
    fn e_shstrndx(&self) -> u16;
    fn e_shoff_set(&mut self, off: u64);
    fn e_phoff_set(&mut self, off: u64);
    fn e_shnum_set(&mut self, num: u16);
}

impl ElfHdr for Elf32_Ehdr {
    fn e_ident(&self) -> &[u8] {
        unsafe { core::slice::from_raw_parts(&self.e_ident[0], EI_NIDENT) }
    }
    fn e_endian(&self) -> u8 {
        self.e_ident[EI_DATA]
    }
    fn e_class(&self) -> u8 {
        self.e_ident[EI_CLASS]
    }
    fn e_machine(&self) -> u16 {
        self.e_machine
    }
    fn e_version(&self) -> u32 {
        self.e_version
    }
    fn e_entry(&self) -> u64 {
        self.e_entry as u64
    }
    fn e_phoff(&self) -> u64 {
        self.e_phoff as u64
    }
    fn e_shoff(&self) -> u64 {
        self.e_shoff as u64
    }
    fn e_flags(&self) -> u64 {
        self.e_flags as u64
    }
    fn e_ehsize(&self) -> u16 {
        self.e_ehsize
    }
    fn e_phentsize(&self) -> u16 {
        self.e_phentsize
    }
    fn e_phnum(&self) -> u16 {
        self.e_phnum
    }
    fn e_shentsize(&self) -> u16 {
        self.e_shentsize
    }
    fn e_shnum(&self) -> u16 {
        self.e_shnum
    }
    fn e_shstrndx(&self) -> u16 {
        self.e_shstrndx
    }
    fn e_shoff_set(&mut self, off: u64) {
        self.e_shoff = off as u32;
    }
    fn e_phoff_set(&mut self, off: u64) {
        self.e_phoff = off as u32;
    }
    fn e_shnum_set(&mut self, num: u16) {
        self.e_shnum = num;
    }
}

impl ElfHdr for Elf64_Ehdr {
    fn e_ident(&self) -> &[u8] {
        unsafe { core::slice::from_raw_parts(&self.e_ident[0], EI_NIDENT) }
    }
    fn e_endian(&self) -> u8 {
        self.e_ident[EI_DATA]
    }
    fn e_class(&self) -> u8 {
        self.e_ident[EI_CLASS]
    }
    fn e_machine(&self) -> u16 {
        self.e_machine
    }
    fn e_version(&self) -> u32 {
        self.e_version
    }
    fn e_entry(&self) -> u64 {
        self.e_entry
    }
    fn e_phoff(&self) -> u64 {
        self.e_phoff
    }
    fn e_shoff(&self) -> u64 {
        self.e_shoff
    }
    fn e_flags(&self) -> u64 {
        self.e_flags as u64
    }
    fn e_ehsize(&self) -> u16 {
        self.e_ehsize
    }
    fn e_phentsize(&self) -> u16 {
        self.e_phentsize
    }
    fn e_phnum(&self) -> u16 {
        self.e_phnum
    }
    fn e_shentsize(&self) -> u16 {
        self.e_shentsize
    }
    fn e_shnum(&self) -> u16 {
        self.e_shnum
    }
    fn e_shstrndx(&self) -> u16 {
        self.e_shstrndx
    }
    fn e_shoff_set(&mut self, off: u64) {
        self.e_shoff = off;
    }
    fn e_phoff_set(&mut self, off: u64) {
        self.e_phoff = off;
    }
    fn e_shnum_set(&mut self, num: u16) {
        self.e_shnum = num;
    }
}

pub trait SectionHdr {
    fn sh_name(&self) -> u32;
    fn sh_type(&self) -> u32;
    fn sh_flags(&self) -> u64;
    fn sh_addr(&self) -> u64;
    fn sh_offset(&self) -> u64;
    fn sh_size(&self) -> u64;
    fn sh_link(&self) -> u32;
    fn sh_info(&self) -> u32;
    fn sh_addralign(&self) -> u64;
    fn sh_entsize(&self) -> u64;
    fn sh_offset_set(&mut self, offset: u64);
    fn sh_size_set(&mut self, offset: u64);
    fn sh_name_set(&mut self, name: u32);
    fn sh_addr_set(&mut self, addr: u64);
    fn sh_type_set(&mut self, ty: u32);
}

impl SectionHdr for Elf32_Shdr {
    fn sh_name(&self) -> u32 {
        self.sh_name
    }
    fn sh_type(&self) -> u32 {
        self.sh_type
    }
    fn sh_flags(&self) -> u64 {
        self.sh_flags as u64
    }
    fn sh_addr(&self) -> u64 {
        self.sh_addr as u64
    }
    fn sh_offset(&self) -> u64 {
        self.sh_offset as u64
    }
    fn sh_size(&self) -> u64 {
        self.sh_size as u64
    }
    fn sh_link(&self) -> u32 {
        self.sh_link
    }
    fn sh_info(&self) -> u32 {
        self.sh_info
    }
    fn sh_addralign(&self) -> u64 {
        self.sh_addralign as u64
    }
    fn sh_entsize(&self) -> u64 {
        self.sh_entsize as u64
    }
    fn sh_offset_set(&mut self, offset: u64) {
        self.sh_offset = offset as u32;
    }
    fn sh_size_set(&mut self, size: u64) {
        self.sh_size = size as u32;
    }
    fn sh_name_set(&mut self, name: u32) {
        self.sh_name = name;
    }
    fn sh_addr_set(&mut self, addr: u64) {
        self.sh_addr = addr as u32;
    }
    fn sh_type_set(&mut self, ty: u32) {
        self.sh_type = ty;
    }
}

impl SectionHdr for Elf64_Shdr {
    fn sh_name(&self) -> u32 {
        self.sh_name
    }
    fn sh_type(&self) -> u32 {
        self.sh_type
    }
    fn sh_flags(&self) -> u64 {
        self.sh_flags
    }
    fn sh_addr(&self) -> u64 {
        self.sh_addr
    }
    fn sh_offset(&self) -> u64 {
        self.sh_offset
    }
    fn sh_size(&self) -> u64 {
        self.sh_size
    }
    fn sh_link(&self) -> u32 {
        self.sh_link
    }
    fn sh_info(&self) -> u32 {
        self.sh_info
    }
    fn sh_addralign(&self) -> u64 {
        self.sh_addralign
    }
    fn sh_entsize(&self) -> u64 {
        self.sh_entsize
    }
    fn sh_offset_set(&mut self, offset: u64) {
        self.sh_offset = offset;
    }
    fn sh_size_set(&mut self, size: u64) {
        self.sh_size = size;
    }
    fn sh_name_set(&mut self, name: u32) {
        self.sh_name = name;
    }
    fn sh_addr_set(&mut self, addr: u64) {
        self.sh_addr = addr;
    }
    fn sh_type_set(&mut self, ty: u32) {
        self.sh_type = ty;
    }
}

pub trait SegmentHdr {
    fn ph_type(&self) -> u32;
    fn ph_flags(&self) -> u32;
    fn ph_offset(&self) -> u64;
    fn ph_vaddr(&self) -> u64;
    fn ph_paddr(&self) -> u64;
    fn ph_filesz(&self) -> u64;
    fn ph_memsz(&self) -> u64;
    fn ph_align(&self) -> u64;
    fn ph_offset_set(&mut self, offset: u64);
    fn ph_vaddr_set(&mut self, addr: u64);
    fn ph_paddr_set(&mut self, addr: u64);
    fn ph_filesz_set(&mut self, addr: u64);
    fn ph_memsz_set(&mut self, addr: u64);
}

impl SegmentHdr for Elf32_Phdr {
    fn ph_type(&self) -> u32 {
        self.ph_type
    }
    fn ph_flags(&self) -> u32 {
        self.ph_flags
    }
    fn ph_offset(&self) -> u64 {
        self.ph_offset as u64
    }
    fn ph_vaddr(&self) -> u64 {
        self.ph_vaddr as u64
    }
    fn ph_paddr(&self) -> u64 {
        self.ph_paddr as u64
    }
    fn ph_filesz(&self) -> u64 {
        self.ph_filesz as u64
    }
    fn ph_memsz(&self) -> u64 {
        self.ph_memsz as u64
    }
    fn ph_align(&self) -> u64 {
        self.ph_align as u64
    }
    fn ph_offset_set(&mut self, offset: u64) {
        self.ph_offset = offset as u32;
    }
    fn ph_vaddr_set(&mut self, addr: u64) {
        self.ph_vaddr = addr as u32;
    }
    fn ph_paddr_set(&mut self, addr: u64) {
        self.ph_paddr = addr as u32;
    }
    fn ph_filesz_set(&mut self, sz: u64) {
        self.ph_filesz = sz as u32;
    }
    fn ph_memsz_set(&mut self, sz: u64) {
        self.ph_memsz = sz as u32;
    }
}

impl SegmentHdr for Elf64_Phdr {
    fn ph_type(&self) -> u32 {
        self.ph_type
    }
    fn ph_flags(&self) -> u32 {
        self.ph_flags
    }
    fn ph_offset(&self) -> u64 {
        self.ph_offset
    }
    fn ph_vaddr(&self) -> u64 {
        self.ph_vaddr
    }
    fn ph_paddr(&self) -> u64 {
        self.ph_paddr
    }
    fn ph_filesz(&self) -> u64 {
        self.ph_filesz
    }
    fn ph_memsz(&self) -> u64 {
        self.ph_memsz
    }
    fn ph_align(&self) -> u64 {
        self.ph_align
    }
    fn ph_offset_set(&mut self, offset: u64) {
        self.ph_offset = offset;
    }
    fn ph_vaddr_set(&mut self, addr: u64) {
        self.ph_vaddr = addr;
    }
    fn ph_paddr_set(&mut self, addr: u64) {
        self.ph_paddr = addr;
    }
    fn ph_filesz_set(&mut self, sz: u64) {
        self.ph_filesz = sz;
    }
    fn ph_memsz_set(&mut self, sz: u64) {
        self.ph_memsz = sz;
    }
}

pub trait Symbol {
    fn st_name(&self) -> u32;
    fn st_info(&self) -> u8;
    fn st_other(&self) -> u8;
    fn st_shndx(&self) -> u16;
    fn st_value(&self) -> u64;
    fn st_size(&self) -> u64;
    fn st_name_set(&mut self, name: u32);
}

impl Symbol for Elf32_Sym {
    fn st_name(&self) -> u32 {
        self.st_name
    }
    fn st_info(&self) -> u8 {
        self.st_info
    }
    fn st_other(&self) -> u8 {
        self.st_other
    }
    fn st_shndx(&self) -> u16 {
        self.st_shndx
    }
    fn st_value(&self) -> u64 {
        self.st_value as u64
    }
    fn st_size(&self) -> u64 {
        self.st_size as u64
    }
    fn st_name_set(&mut self, name: u32) {
        self.st_name = name;
    }
}

impl Symbol for Elf64_Sym {
    fn st_name(&self) -> u32 {
        self.st_name
    }
    fn st_info(&self) -> u8 {
        self.st_info
    }
    fn st_other(&self) -> u8 {
        self.st_other
    }
    fn st_shndx(&self) -> u16 {
        self.st_shndx
    }
    fn st_value(&self) -> u64 {
        self.st_value
    }
    fn st_size(&self) -> u64 {
        self.st_size
    }
    fn st_name_set(&mut self, name: u32) {
        self.st_name = name;
    }
}

pub trait Relocation {
    fn r_offset(&self) -> u64;
    fn r_sym(&self) -> u32;
    fn r_type(&self) -> u32;
}

impl Relocation for Elf32_Rel {
    fn r_offset(&self) -> u64 {
        self.r_offset as u64
    }
    fn r_sym(&self) -> u32 {
        self.r_info >> 8
    }
    fn r_type(&self) -> u32 {
        self.r_info & 0xFF
    }
}

impl Relocation for Elf64_Rel {
    fn r_offset(&self) -> u64 {
        self.r_offset
    }
    fn r_sym(&self) -> u32 {
        (self.r_info >> 32) as u32
    }
    fn r_type(&self) -> u32 {
        (self.r_info & 0xFFFFFFFF) as u32
    }
}

pub trait RelocationEx: Relocation {
    fn r_addend(&self) -> i64;
}

impl Relocation for Elf32_Rela {
    fn r_offset(&self) -> u64 {
        self.r_offset as u64
    }
    fn r_sym(&self) -> u32 {
        self.r_info >> 8
    }
    fn r_type(&self) -> u32 {
        self.r_info & 0xFF
    }
}

impl RelocationEx for Elf32_Rela {
    fn r_addend(&self) -> i64 {
        self.r_addend as i64
    }
}

impl Relocation for Elf64_Rela {
    fn r_offset(&self) -> u64 {
        self.r_offset
    }
    fn r_sym(&self) -> u32 {
        (self.r_info >> 32) as u32
    }
    fn r_type(&self) -> u32 {
        (self.r_info & 0xFFFFFFFF) as u32
    }
}

impl RelocationEx for Elf64_Rela {
    fn r_addend(&self) -> i64 {
        self.r_addend
    }
}

pub trait Dynamic {
    fn d_tag(&self) -> u64;
    fn d_val_ptr(&self) -> u64;
    fn d_val_ptr_set(&mut self, ptr: u64);
}

impl Dynamic for Elf32_Dyn {
    fn d_tag(&self) -> u64 {
        self.d_tag as u64
    }
    fn d_val_ptr(&self) -> u64 {
        self.d_val_ptr as u64
    }
    fn d_val_ptr_set(&mut self, ptr: u64) {
        self.d_val_ptr = ptr as u32;
    }
}

impl Dynamic for Elf64_Dyn {
    fn d_tag(&self) -> u64 {
        self.d_tag
    }
    fn d_val_ptr(&self) -> u64 {
        self.d_val_ptr
    }
    fn d_val_ptr_set(&mut self, ptr: u64) {
        self.d_val_ptr = ptr;
    }
}

pub trait ElfBits {
    type ElfHdr: ElfHdr + Endian + Clone;
    type SectionHdr: SectionHdr + Clone + Endian;
    type SegmentHdr: SegmentHdr + Clone + Endian;
    type Symbol: Symbol + Clone + Endian;
    type Relocation: Relocation + Clone + Endian;
    type RelocationEx: RelocationEx + Clone + Endian;
    type Dynamic: Dynamic + Clone + Endian;
    type Bits: Endian
        + BitAnd<Self::Bits, Output = Self::Bits>
        + BitOr<Self::Bits, Output = Self::Bits>
        + BitAndAssign
        + BitOrAssign
        + PartialEq<Self::Bits>
        + Copy
        + Default;
    const CLASS: u8;
    fn from(val: u64) -> Self::Bits;
}

impl ElfBits for u32 {
    type ElfHdr = Elf32_Ehdr;
    type SectionHdr = Elf32_Shdr;
    type SegmentHdr = Elf32_Phdr;
    type Symbol = Elf32_Sym;
    type Relocation = Elf32_Rel;
    type RelocationEx = Elf32_Rela;
    type Dynamic = Elf32_Dyn;
    type Bits = u32;
    const CLASS: u8 = ELFCLASS32;
    fn from(val: u64) -> Self::Bits {
        val as u32
    }
}

impl ElfBits for u64 {
    type ElfHdr = Elf64_Ehdr;
    type SectionHdr = Elf64_Shdr;
    type SegmentHdr = Elf64_Phdr;
    type Symbol = Elf64_Sym;
    type Relocation = Elf64_Rel;
    type RelocationEx = Elf64_Rela;
    type Dynamic = Elf64_Dyn;
    type Bits = u64;
    const CLASS: u8 = ELFCLASS64;
    fn from(val: u64) -> Self::Bits {
        val
    }
}

impl Elf_GnuHash {
    pub fn hash<T>(&self, name: &[u8]) -> (u32, u32, u32, T::Bits)
    where
        T: ElfBits,
    {
        let w = core::mem::size_of::<T::Bits>() as u32 * 8;
        let h1 = gnu_hash(name);
        let h2 = h1 >> self.gh_bmshift;
        let n = (h1 / w) & (self.gh_bmmask - 1);
        let b1 = h1 % w;
        let b2 = h2 % w;
        let bitmask = (1 << b1 as u64) | (1 << b2 as u64);
        (h1, h1 % self.gh_nbuckets, n, T::from(bitmask))
    }

    pub fn bm_offset<T>(&self, bmndx: u32) -> usize
    where
        T: ElfBits,
    {
        core::mem::size_of::<Self>() + core::mem::size_of::<T::Bits>() * bmndx as usize
    }

    pub fn hb_offset<T>(&self, nbucket: u32) -> usize
    where
        T: ElfBits,
    {
        core::mem::size_of::<Self>()
            + core::mem::size_of::<T::Bits>() * self.gh_bmmask as usize
            + core::mem::size_of::<u32>() * nbucket as usize
    }

    pub fn hv_offset<T>(&self, symndx: u32) -> usize
    where
        T: ElfBits,
    {
        core::mem::size_of::<Self>()
            + core::mem::size_of::<T::Bits>() * self.gh_bmmask as usize
            + core::mem::size_of::<u32>() * (symndx + self.gh_nbuckets - self.gh_symndx) as usize
    }
}
